__all__ = ["Mesh"]

from graphicobject import *
from halfedge import *
from OpenGL.GL import *
from bbox import *
from math import sqrt

class Mesh(GraphicObject):
    "A Mesh object"
    
    def __init__(self,face=[],vertex=[],
                 drawmode="fill",normalmode="face",
                 material=None,transformation=None):
        """Constructor.         
        @param face: an array of lists of vertex indices. 
        @param vertex: an array of 4-tuples, each representing the coordinates
           of a vertex. 
        @param drawmode: may be either "fill", meaning 
           that faces will be drawn filled, "line", meaning that only edges will
           be drawn, or "both", meaning both fill and line rendering. 
        @param normalmode: may be either "face" meaning one normal per face, or 
           "vertex", meaning that normals will be smoothed per vertex.
        @param material: the material object (Material). If None, means that
          no material is attached to this object and thus it will be rendered
          with the current material.
        @param transformation: a 4x4 transformation matrix stored by column. If None, 
          then an identity transformation is meant.
        """
        GraphicObject.__init__(self,BoundingBox.fromPoints(vertex),
                               material,transformation)
        self.hds = HalfedgeDS(face,vertex)
        self.vertex = vertex
        self._drawmode = drawmode
        self._normalmode = normalmode
        self._displaylist = None
        self.faceNormal = [None]*len(face)
        self.vertexNormal = [None]*len(vertex)
        self.computeFaceNormals()
        self.computeVertexNormals()
    
    def _changed (self):
        """Invalidate display list."""
        self._displaylist = None
        
    def _setDrawMode (self, drawmode):
        "Setter for drawmode"
        self._drawmode = drawmode
        self._changed ()
    
    def _getDrawMode (self):
        "Getter for drawmode"
        return self._drawmode
    
    drawMode = property (_getDrawMode, _setDrawMode)
            
    def _setNormalMode (self, normalmode):
        "Setter for normalmode"
        self._normalmode = normalmode
        self._changed()
    
    def _getNormalMode (self):
        "Getter for normalmode"
        return self._normalmode
    
    normalMode = property (_getNormalMode, _setNormalMode)
                
    def computeFaceNormals(self):
        "For all faces f, stores in self.faceNormal[f] the normal of face f."
        for h in self.hds.allFaces():
            a, b, c = 0.0, 0.0, 0.0
            vtx = map(lambda hh: self.vertex[hh.vtx], self.hds.aroundFace(h.fac))
            for i in range(len (vtx)):
                x0,y0,z0 = vtx[i-1]
                x1,y1,z1 = vtx[i]
                c += (y0+y1)*(x0-x1)
                b += (x0+x1)*(z0-z1)
                a += (z0+z1)*(y0-y1)
            sz = sqrt(a*a+b*b+c*c)
            self.faceNormal[h.fac] = (a/sz,b/sz,c/sz)
    
    def computeVertexNormals(self):
        "For all vertices v, stores in self.vertexNormal[v] a smoothed normal"
        for h in self.hds.allVertices():
            a,b,c = 0.0, 0.0, 0.0
            for hh in self.hds.aroundVertex(h.vtx):
                if hh.fac not in self.hds.borderface:
                    # Consider only real faces, not "fake" border faces.
                    x,y,z = self.faceNormal[hh.fac]
                    a,b,c = a+x,b+y,c+z
            sz = sqrt(a*a+b*b+c*c)
            self.vertexNormal[h.vtx] = (a/sz,b/sz,c/sz)
               
    def draw(self):
        "Draws the mesh."
        GraphicObject.draw(self)
        if self._displaylist != None: 
            glCallList (self._displaylist)
        else:
            self._displaylist = glGenLists (1)
            glNewList (self._displaylist, GL_COMPILE_AND_EXECUTE)
            if self._drawmode == "fill": 
                self.drawFill()
            elif self._drawmode == "line": 
                self.drawLine()
            else:
                self.drawFill()
                glPushAttrib(GL_POLYGON_BIT)
                glEnable (GL_POLYGON_OFFSET_LINE)
                self.drawLine()
                glPopAttrib()
            glEndList ()
            
    def drawFill(self):
        """Draws the mesh in fill mode"""
        if self._normalmode == "face":
            for h in self.hds.allFaces():
                glBegin(GL_POLYGON)
                glNormal3fv (self.faceNormal[h.fac])
                for hh in self.hds.aroundFace(h.fac):
                    glVertex3fv(self.vertex[hh.vtx])
                glEnd()
        else:
            for h in self.hds.allFaces():
                glBegin(GL_POLYGON)                
                for hh in self.hds.aroundFace(h.fac):
                    glNormal3fv (self.vertexNormal[hh.vtx])
                    glVertex3fv (self.vertex[hh.vtx])
                glEnd()
                
    def drawLine(self):
        """"Draws the mesh in line mode."""
        glPushAttrib (GL_LIGHTING_BIT)
        glDisable(GL_LIGHTING)
        glBegin (GL_LINES)
        for h in self.hds.allEdges():
            glVertex3fv (self.vertex[h.vtx])
            glVertex3fv (self.vertex[self.hds[h.prv].vtx])
        glEnd ()
        glPopAttrib ()
        
if __name__=="__main__":
    # A simple test
    mesh = Mesh([(0,1,2)],[(0,0,0),(1,0,0),(0,1,0)])
